/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;

using Anculus.Core;

using Galaxium.Client;

namespace Galaxium.Protocol.Msn
{
	[MsnP2PApplication (2, "5D3E02AB-6190-11D3-BBBB-00C04F795683")]
	public class P2PFileTransfer : AbstractMsnP2PSessionApplication
	{
		public event EventHandler Progressed;
		
		FTContext _context;
		Stream _data;
		bool _sending;
		bool _sendingData;
		P2PMessage _dataMsg;
		MsnFileTransfer _fileTransfer;
		
		public FTContext Context
		{
			get { return _context; }
		}
		
		public Stream DataStream
		{
			get { return _data; }
			set { _data = value; }
		}
		
		public bool Sending
		{
			get { return _sending; }
		}
		
		public long Transferred
		{
			get
			{
				if (Sending)
					return _data.Position;
				
				return _data.Length;
			}
		}
		
		public P2PFileTransfer (MsnP2PSession p2pSession)
			: base (p2pSession)
		{
			_context = new FTContext (Convert.FromBase64String (P2PSession.Invite.MIMEBody["Context"].Value));
			_sending = false;
			
			_data = File.OpenWrite (Path.Combine (FileTransferUtility.DestinationFolder, _context.Filename));
			
			_fileTransfer = new MsnFileTransfer (Session, Remote, this);
			FileTransferUtility.Add (_fileTransfer);
			
			P2PSession.Waiting += P2PSessionWaiting;
		}
		
		public P2PFileTransfer (MsnContact remote, Stream data, string filename)
			: base (remote.Session.Account as MsnAccount, remote)
		{
			_context = new FTContext (filename, (ulong)data.Length);
			_data = data;
			_sending = true;
		}
		
		public P2PFileTransfer (MsnContact remote, string filename)
			: this (remote, File.OpenRead (filename), Path.GetFileName (filename))
		{
		}
		
		public override void Dispose ()
		{
			if (_data != null)
				_data.Close ();
			
			base.Dispose ();
		}
		
		void P2PSessionWaiting (object sender, EventArgs args)
		{
			if ((P2PSession.State == MsnP2PSessionState.WaitingForLocal) && (_fileTransfer != null))
			{
				P2PSession.Waiting -= P2PSessionWaiting;
				
				ActivityUtility.EmitActivity (this, new ReceivedFileActivity(Session, _fileTransfer));
				Session.EmitTransferInvitationReceived (new FileTransferEventArgs (_fileTransfer));
			}
		}
		
		protected void OnProgressed ()
		{
			if (this.Progressed != null)
				this.Progressed (this, EventArgs.Empty);
		}
		
		public override bool CheckInvite (SLPRequestMessage invite)
		{
			try
			{
				FTContext context = new FTContext (Convert.FromBase64String (invite.MIMEBody["Context"].Value));
				Log.Debug ("{0} ({1} bytes) (base {2})", context.Filename, context.FileSize, base.CheckInvite (invite));
				// Invite is valid if it has a filename and the file size is greater than 0
				return base.CheckInvite (invite) && (!string.IsNullOrEmpty (context.Filename)) && (context.FileSize > 0);
			}
			catch (Exception ex)
			{
				// We can't parse the context, so refuse the invite
				Log.Error (ex, "Unable to parse file transfer invite");
				Log.Debug ("{0}", invite);
				
				return false;
			}
		}
		
		public override string CreateInviteContext ()
		{
			return Convert.ToBase64String (_context.ToByteArray ());
		}
		
		public override void Begin ()
		{
			base.Begin ();
			
			if (Sending)
			{
				_data.Seek (0, SeekOrigin.Begin);
				
				_dataMsg = new P2PMessage (Session);
				_dataMsg.Header.MessageID = P2PSession.NextID ();
				_dataMsg.Header.Flags = P2PHeaderFlag.FileData;
				_dataMsg.Header.TotalSize = (ulong)_data.Length;
				
				_sendingData = true;
				
				SendChunk ();
			}
		}
		
		protected override void ProcessOutQueue (bool forceReady)
		{
			if (_sendingData && (Bridge.Ready || forceReady) && (OutQueue.Count == 0))
				SendChunk ();
			else
				base.ProcessOutQueue (forceReady);
		}
		
		void SendChunk ()
		{
			_dataMsg.Header.ChunkOffset = (ulong)_data.Position;
			
			byte[] data = new byte[Bridge.MaxDataSize];
			int read = _data.Read (data, 0, data.Length);
			
			//Log.Debug ("Sending {0} bytes ({1} to {2} of {3})", read, _dataMsg.Header.ChunkOffset, _data.Position - 1, _data.Length);
			
			if (read < data.Length)
			{
				byte[] newData = new byte[read];
				Array.Copy (data, newData, newData.Length);
				data = newData;
			}
			
			_dataMsg.Payload = data;
			
			/*Log.Debug ("SessionID: {0}", _dataMsg.Header.SessionID);
			Log.Debug ("MessageID: {0}", _dataMsg.Header.MessageID);
			Log.Debug ("Offset:    {0}", _dataMsg.Header.ChunkOffset);
			Log.Debug ("TotalSize: {0}", _dataMsg.Header.TotalSize);
			Log.Debug ("ChunkSize: {0}", _dataMsg.Header.ChunkSize);
			Log.Debug ("Flags:     {0}", _dataMsg.Header.Flags);
			Log.Debug ("AckID:     {0}", _dataMsg.Header.AckID);
			Log.Debug ("AckUID:    {0}", _dataMsg.Header.AckUID);
			Log.Debug ("AckSize:   {0}", _dataMsg.Header.AckSize);*/
			
			if (_data.Position == _data.Length)
			{
				// This is the last chunk of data, register the ackHandler
				Send (_dataMsg, delegate
				{
					OnComplete ();
				});
				
				_sendingData = false;
			}
			else
				Send (_dataMsg, null);
			
			OnProgressed ();
		}
		
		public override bool ProcessMessage (IMsnP2PBridge bridge, P2PMessage msg)
		{
			if (((msg.Header.Flags & P2PHeaderFlag.Data) == P2PHeaderFlag.Data) ||
			    (msg.Header.Flags == P2PHeaderFlag.Normal))
			{
				_data.Write (msg.Payload, 0, msg.Payload.Length);
				
				OnProgressed ();
				
				if (_data.Length == (long)_context.FileSize)
				{
					// Finished transfer
					
					Send (msg.CreateAck ());
					
					OnComplete ();
					P2PSession.Close ();
				}
				
				return true;
			}
			
#if debug
			return false;
#else
			// If we're not debugging then we should try to continue even if we don't understand this message
			return true;
#endif
		}
	}
}
